package com.spzx.order.service.impl;

import java.util.Date;

import java.util.*;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.spzx.cart.api.RemoteCartService;
import com.spzx.cart.domain.CartInfo;
import com.spzx.common.core.constant.CacheConstants;
import com.spzx.common.core.constant.MqConstants;
import com.spzx.common.core.constant.SecurityConstants;
import com.spzx.common.core.context.SecurityContextHolder;
import com.spzx.common.core.domain.R;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.core.utils.DateUtils;
import com.spzx.common.core.utils.StringUtils;
import com.spzx.common.core.utils.uuid.UUID;
import com.spzx.common.rabbit.service.RabbitService;
import com.spzx.common.security.utils.SecurityUtils;
import com.spzx.order.domain.OrderForm;
import com.spzx.order.domain.OrderInfo;
import com.spzx.order.domain.OrderItem;
import com.spzx.order.domain.OrderLog;
import com.spzx.order.domain.vo.TradeVo;
import com.spzx.order.mapper.OrderInfoMapper;
import com.spzx.order.mapper.OrderItemMapper;
import com.spzx.order.mapper.OrderLogMapper;
import com.spzx.order.service.IOrderInfoService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spzx.product.api.RemoteProductService;
import com.spzx.product.api.RemoteSkuStockService;
import com.spzx.product.domain.ProductSku;
import com.spzx.product.domain.SkuPrice;
import com.spzx.product.domain.vo.SkuLockVo;
import com.spzx.user.api.RemoteUserAddressService;
import com.spzx.user.api.RemoteUserInfoService;
import com.spzx.user.domain.UserAddress;
import com.spzx.user.domain.UserInfo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 订单 服务实现类
 * </p>
 *
 * @author xiongkunYang
 * @since 2025/06/05 09:52
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements IOrderInfoService {

    private final RemoteCartService remoteCartService;
    private final RemoteProductService remoteProductService;
    private final RemoteSkuStockService remoteSkuStockService;
    private final RemoteUserAddressService remoteUserAddressService;
    private final RemoteUserInfoService remoteUserInfoService;
    private final RedisTemplate<Object, Object> redisTemplate;
    private final OrderItemMapper orderItemMapper;
    private final OrderLogMapper orderLogMapper;
    private final RabbitService rabbitService;

    @Override
    public TradeVo orderTradeData() {
        // 获取当前登录用户的id
        Long userId = SecurityContextHolder.getUserId();

        R<List<CartInfo>> cartCheckedListResult = remoteCartService.getCartCheckedList(userId, SecurityConstants.INNER);
        if (R.FAIL == cartCheckedListResult.getCode()) {
            throw new ServiceException(cartCheckedListResult.getMsg());
        }
        List<CartInfo> cartCheckedList = cartCheckedListResult.getData();
        Assert.notEmpty(cartCheckedList, "购物车无选中商品");

        TradeVo tradeVo = new TradeVo();
        //  @Schema(description = "结算商品列表") private List<OrderItem> orderItemList;
        List<OrderItem> orderItemList = new java.util.ArrayList<>();
        //  @Schema(description = "结算总金额")private BigDecimal totalAmount;
        BigDecimal totalAmount = BigDecimal.ZERO;
        for (CartInfo cartInfo : cartCheckedList) {
            OrderItem orderItem = new OrderItem();
            BeanUtils.copyProperties(cartInfo, orderItem);
            orderItemList.add(orderItem);
            totalAmount = totalAmount.add(orderItem.getSkuPrice().multiply(new BigDecimal(orderItem.getSkuNum())));
        }
        tradeVo.setOrderItemList(orderItemList);
        tradeVo.setTotalAmount(totalAmount);
        //  @Schema(description = "交易号")private String tradeNo;
        tradeVo.setTradeNo(generateTradeNo(userId));
        return tradeVo;
    }

    @Transactional(rollbackFor = {Exception.class})
    @Override
    public Long submitOrder(OrderForm orderForm) {
        //1 流水号校验，防止订单重复提交&防止页面停留超时
        //获取用户的userId
        Long userId = SecurityUtils.getUserId();
        String userTradeKey = CacheConstants.USER_TRADE_KEY + userId;
        String tradeNo = orderForm.getTradeNo();
        // 采用Lua脚本保证判断删除流水号原子性
        DefaultRedisScript<Long> redisScript = getRedisScript();
        long flag = redisTemplate.execute(redisScript, List.of(userTradeKey), tradeNo);
        if (flag == 0) {
            throw new ServiceException("操作超时，请退回重试");
        }
        if (flag == -1) {
            throw new ServiceException("请勿重复提交订单");
        }
        //2 判断价格是否改变并更新购物车中的商品价格
        List<OrderItem> orderItemList = orderForm.getOrderItemList();
        Assert.notEmpty(orderItemList, "结算商品列表为空");

        List<Long> skuIdList = orderItemList.stream().map(OrderItem::getSkuId).toList();
        R<List<SkuPrice>> skuPriceListResult = this.remoteProductService.getSkuPriceList(skuIdList, SecurityConstants.INNER);
        if (R.FAIL == skuPriceListResult.getCode()) {
            throw new ServiceException(skuPriceListResult.getMsg());
        }
        List<SkuPrice> skuPriceVoList = skuPriceListResult.getData();

        Map<Long, BigDecimal> skuPriceMap = skuPriceVoList.stream()
                .collect(Collectors.toMap(SkuPrice::getSkuId, SkuPrice::getSalePrice));

        StringBuilder priceCheckResult = new StringBuilder();
        for (OrderItem orderItem : orderItemList) {
            if (orderItem.getSkuPrice().compareTo(skuPriceMap.get(orderItem.getSkuId())) != 0) {
                priceCheckResult.append(orderItem.getSkuName()).append("价格变化了; ");
            }
        }

        if (!priceCheckResult.isEmpty()) {
            // 更新购物车中的价格
            R<Boolean> updateCartPriceResult = this.remoteCartService.updateCartPrice(userId, SecurityConstants.INNER);
            if (R.FAIL == updateCartPriceResult.getCode()) {
                throw new ServiceException(updateCartPriceResult.getMsg());
            }
            throw new ServiceException(priceCheckResult + "请重新下单！");
        }

        // 3 校验并锁定库存
        List<SkuLockVo> skuLockVoList = orderItemList.stream()
                .map(orderItem -> {
                    SkuLockVo skuLockVo = new SkuLockVo();
                    BeanUtils.copyProperties(orderItem, skuLockVo);
                    return skuLockVo;
                })
                .toList();
        R<String> errorMsgResult = this.remoteSkuStockService.checkAndLockStock(tradeNo, skuLockVoList, SecurityConstants.INNER);
        if (R.FAIL == errorMsgResult.getCode()) {
            throw new ServiceException(errorMsgResult.getMsg());
        }
        if (StringUtils.hasText(errorMsgResult.getData())) {
            throw new ServiceException(errorMsgResult.getData());
        }

        // 4 保存订单,发生异常时需要解锁库存
        Long orderId;
        try {
            orderId = this.saveOrder(orderForm);
        } catch (Exception e) {
            log.error("保存订单失败:{}", e.getMessage(), e);
            // 下单失败，解锁库存
            rabbitService.sendMessage(MqConstants.EXCHANGE_PRODUCT, MqConstants.ROUTING_UNLOCK, tradeNo);
            // 抛出异常
            throw new ServiceException("下单失败");
        }

        // 5 删除购物车选中商品
        R<Boolean> deleteResult = this.remoteCartService.deleteCartCheckedList(userId, SecurityConstants.INNER);
        if (R.FAIL == deleteResult.getCode()) {
            log.error("删除购物车选中商品失败:{}", deleteResult.getMsg());
        }

        // 6 发送延迟关单消息，到期未支付的订单，则关闭，释放库存
        this.rabbitService.sendDelayMessage(
                MqConstants.EXCHANGE_CANCEL_ORDER,
                MqConstants.ROUTING_CANCEL_ORDER, tradeNo,
                MqConstants.CANCEL_ORDER_DELAY_TIME
        );

        return orderId;
    }

    @Transactional(rollbackFor = Exception.class)
    public Long saveOrder(OrderForm orderForm) {
        // 获取用户信息
        Long userId = SecurityUtils.getUserId();
        String userName = SecurityUtils.getUsername();
        R<UserInfo> userInfoResult = this.remoteUserInfoService.getUserInfo(userName, SecurityConstants.INNER);
        if (R.FAIL == userInfoResult.getCode()) {
            throw new ServiceException(userInfoResult.getMsg());
        }
        UserInfo userInfo = userInfoResult.getData();

        // 订单信息
        OrderInfo orderInfo = new OrderInfo();
        // 远程调用获取用户地址信息
        R<UserAddress> userAddressResult = remoteUserAddressService.getUserAddress(orderForm.getUserAddressId(), SecurityConstants.INNER);
        if (R.FAIL == userAddressResult.getCode()) {
            throw new ServiceException(userAddressResult.getMsg());
        }
        // 获取用户地址信息
        UserAddress userAddress = userAddressResult.getData();
        // 实时计算orderItem中商品的总价格
        List<OrderItem> orderItemList = orderForm.getOrderItemList();
        BigDecimal totalAmount = orderItemList
                .stream()
                .map(orderItem -> orderItem.getSkuPrice().multiply(new BigDecimal(orderItem.getSkuNum())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        orderInfo.setUserId(userId);
        orderInfo.setNickName(userInfo.getNickName());
        orderInfo.setOrderNo(orderForm.getTradeNo());
        orderInfo.setTotalAmount(totalAmount);
        orderInfo.setCouponAmount(new BigDecimal(0));
        orderInfo.setOriginalTotalAmount(new BigDecimal(0));
        orderInfo.setFeightFee(totalAmount);
        orderInfo.setOrderStatus(0);
        orderInfo.setReceiverName(userAddress.getName());
        orderInfo.setReceiverPhone(userAddress.getPhone());
        orderInfo.setReceiverTagName(userAddress.getTagName());
        orderInfo.setReceiverProvince(userAddress.getProvinceCode());
        orderInfo.setReceiverCity(userAddress.getCityCode());
        orderInfo.setReceiverDistrict(userAddress.getDistrictCode());
        orderInfo.setReceiverAddress(userAddress.getFullAddress());
        // 默认三天后收货
        orderInfo.setReceiveTime(DateUtils.addDays(new Date(), 3));
        orderInfo.setCreateBy(userName);
        orderInfo.setUpdateBy(userName);
        orderInfo.setRemark(orderForm.getRemark());
        baseMapper.insert(orderInfo);
        Long orderInfoId = orderInfo.getId();

        // 保存订单项记录：order_item
        for (OrderItem orderItem : orderItemList) {
            orderItem.setOrderId(orderInfoId);
            orderItem.setCreateBy(userName);
            orderItem.setUpdateBy(userName);
            orderItemMapper.insert(orderItem);
        }

        // 保存订单日志记录：order_log
        OrderLog orderLog = new OrderLog();
        orderLog.setOrderId(orderInfoId);
        orderLog.setOperateUser(userInfo.getNickName());
        orderLog.setProcessStatus(orderInfo.getOrderStatus());
        orderLog.setNote("提交订单");
        orderLog.setCreateBy(userName);
        orderLog.setUpdateBy(userName);
        orderLogMapper.insert(orderLog);

        return orderInfoId;

    }

    /**
     * 获取一个用于Redis的Lua脚本对象
     * 该方法定义了一个Lua脚本，用于在Redis中执行以下逻辑：
     * - 如果指定键的值等于给定的值，则删除该键，并返回1
     * - 如果指定键存在，但值不匹配，则返回-1
     * - 如果指定键不存在，则返回0
     *
     * @return DefaultRedisScript<Integer> 初始化的Redis脚本对象，包含Lua脚本和结果类型定义
     */
    private static DefaultRedisScript<Long> getRedisScript() {
        // 定义Lua脚本文本
        String scriptText = """
                if redis.call("get",KEYS[1]) == ARGV[1]
                then
                    redis.call("del",KEYS[1])
                    return 1
                else
                    if redis.call('exists', KEYS[1]) == 1
                    then
                           return -1
                       else
                       	return 0
                       end
                end""";
        // 创建一个空的Redis脚本对象
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        // 设置脚本的文本内容
        redisScript.setScriptText(scriptText);
        // 设置脚本执行结果的类型
        redisScript.setResultType(Long.class);
        // 返回初始化的Redis脚本对象
        return redisScript;
    }


    @Override
    public TradeVo buy(Long skuId) {
        Long userId = SecurityUtils.getUserId();
        String username = SecurityUtils.getUsername();

        TradeVo tradeVo = new TradeVo();
        R<ProductSku> productSkuResult = this.remoteProductService.getProductSku(skuId, SecurityConstants.INNER);
        if (R.FAIL == productSkuResult.getCode()) {
            throw new ServiceException(productSkuResult.getMsg());
        }
        ProductSku productSku = productSkuResult.getData();
        tradeVo.setTotalAmount(productSku.getSalePrice());

        OrderItem orderItem = new OrderItem();
        orderItem.setSkuId(skuId);
        orderItem.setSkuName(productSku.getSkuName());
        orderItem.setThumbImg(productSku.getThumbImg());
        orderItem.setSkuPrice(productSku.getSalePrice());
        orderItem.setSkuNum(1);
        orderItem.setCreateBy(username);
        orderItem.setUpdateBy(username);
        tradeVo.setOrderItemList(List.of(orderItem));

        tradeVo.setTradeNo(this.generateTradeNo(userId));
        tradeVo.setIsBuy(true);
        return tradeVo;
    }

    /**
     * 获取用户订单信息列表
     *
     * @param orderStatus 订单状态，用于筛选订单，如果为null，则不按订单状态筛选
     * @return 用户订单信息列表，包含订单详情和订单项
     */
    @Override
    public List<OrderInfo> getUserOrderInfoList(Integer orderStatus) {
        // 获取当前登录用户的id
        Long userId = SecurityUtils.getUserId();

        // 查询用户订单信息列表，根据订单创建时间降序排序
        List<OrderInfo> orderInfoList = this.baseMapper.selectList(new LambdaQueryWrapper<OrderInfo>()
                .eq(OrderInfo::getUserId, userId)
                .eq(orderStatus != null, OrderInfo::getOrderStatus, orderStatus)
                .orderByDesc(OrderInfo::getCreateTime));

        // 如果查询到的订单信息列表不为空，则进一步处理订单项
        if (!CollectionUtils.isEmpty(orderInfoList)) {
            // 提取订单ID列表
            List<Long> orderIds = orderInfoList.stream().map(OrderInfo::getId).toList();

            // 根据订单ID列表查询所有订单项
            List<OrderItem> orderItemList = this.orderItemMapper.selectList(new LambdaQueryWrapper<OrderItem>()
                    .in(OrderItem::getOrderId, orderIds));

            // 将订单项按订单ID分组，便于后续关联到对应的订单信息上
            Map<Long, List<OrderItem>> orderItemMap = orderItemList.stream().collect(Collectors.groupingBy(OrderItem::getOrderId));

            // 遍历订单信息列表，将对应的订单项列表装配到订单信息中
            orderInfoList.forEach(orderInfo -> orderInfo.setOrderItemList(orderItemMap.get(orderInfo.getId())));
        }
        // 返回用户订单信息列表
        return orderInfoList;
    }


    @Override
    public OrderInfo getOrderInfoById(Long orderId) {
        OrderInfo orderInfo = this.baseMapper.selectById(orderId);
        List<OrderItem> orderItemList = this.orderItemMapper.selectList(new LambdaQueryWrapper<OrderItem>()
                .eq(OrderItem::getOrderId, orderId));
        orderInfo.setOrderItemList(orderItemList);
        return orderInfo;
    }

    /**
     * 取消订单方法
     * 当订单存在且状态为待支付时，将订单状态标记为已取消，并记录取消原因和时间
     * 同时，生成一条订单日志以跟踪订单状态的变化
     *
     * @param orderInfo    订单信息对象，包含订单的详细信息
     * @param cancelReason 取消订单的原因
     */
    @Override
    public void cancelOrder(OrderInfo orderInfo, String cancelReason) {
        Assert.notNull(orderInfo, "订单信息不能为空");
        //业务去重，防止重复消费
        String orderNo = orderInfo.getOrderNo();
        String lock = CacheConstants.ORDER_CANCEL_KEY + orderNo;
        Boolean isFirst = redisTemplate.opsForValue().setIfAbsent(lock, orderNo, 1, TimeUnit.MINUTES);
        if (Boolean.FALSE.equals(isFirst)) {
            return;
        }
        // 检查订单是否存在且状态为待支付（0）
        if (orderInfo.getOrderStatus() == 0) {
            // 设置订单状态为已取消（-1）
            orderInfo.setOrderStatus(-1);
            // 记录取消时间
            orderInfo.setCancelTime(new Date());
            // 记录取消原因
            orderInfo.setCancelReason(cancelReason);
            // 更新数据库中的订单信息
            this.baseMapper.updateById(orderInfo);

            // 创建订单日志对象以记录订单状态变化
            OrderLog orderLog = new OrderLog();
            // 设置订单日志的订单ID
            orderLog.setOrderId(orderInfo.getId());
            // 设置订单日志的处理状态为已取消（-1）
            orderLog.setProcessStatus(-1);
            // 设置订单日志的备注信息为取消原因
            orderLog.setNote(cancelReason);
            orderLog.setCreateBy(orderInfo.getCreateBy());
            orderLog.setUpdateBy(orderInfo.getUpdateBy());
            orderLog.setOperateUser(orderInfo.getNickName());
            // 保存订单日志
            this.orderLogMapper.insert(orderLog);
            // 发送MQ消息通知商品系统解锁库存
            this.rabbitService.sendMessage(MqConstants.EXCHANGE_PRODUCT, MqConstants.ROUTING_UNLOCK, orderInfo.getOrderNo());
        }
    }

    @Override
    public OrderInfo getByOrderNo(String orderNo) {
        OrderInfo orderInfo = this.baseMapper.selectOne(new LambdaQueryWrapper<OrderInfo>()
                .eq(OrderInfo::getOrderNo, orderNo));
        List<OrderItem> orderItemList = this.orderItemMapper.selectList(new LambdaQueryWrapper<OrderItem>()
                .eq(OrderItem::getOrderId, orderInfo.getId()));
        orderInfo.setOrderItemList(orderItemList);
        return orderInfo;
    }

    @Transactional(rollbackFor = {Exception.class})
    @Override
    public void processPaySuccess(String orderNo) {
        // 获取订单信息
        OrderInfo orderInfo = this.baseMapper.selectOne(new LambdaQueryWrapper<OrderInfo>()
                .eq(OrderInfo::getOrderNo, orderNo));

        // 幂等性判断
        if (orderInfo.getOrderStatus() == 1) {
            return;
        }
        // 更新订单状态为已支付
        orderInfo.setOrderStatus(1);
        orderInfo.setPaymentTime(new Date());
        this.baseMapper.updateById(orderInfo);

    }


    /**
     * 渲染订单确认页面-生成用户流水号
     *
     * @param userId `
     * @return 订单流水号
     */
    private String generateTradeNo(Long userId) {
        //1.构建流水号Key
        String userTradeKey = CacheConstants.USER_TRADE_KEY + userId;
        //2.构建流水号value
        String tradeNo = UUID.randomUUID().toString().replaceAll("-", "");
        //3.将流水号存入Redis 暂存5分钟
        redisTemplate.opsForValue().set(userTradeKey, tradeNo, 5, TimeUnit.MINUTES);
        return tradeNo;
    }
}
